#region License

/* Copyright (c) 2008, Dusty Candland
 * 
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
 * * Neither the name of Dusty Candland nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#endregion

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;

namespace NS3Library.Amazon
{
    public class RequestSigner
    {
        private readonly string _awsKey;
        private readonly string _awsSecret;
        private readonly string _signHeadersStartingWith = "x-amz-";
        private readonly string _newLine = "\n";
        private readonly string _awsS3HostName = "s3.amazonaws.com";

        public RequestSigner(string awsKey, string awsSecret)
        {
            _awsKey = awsKey;
            _awsSecret = awsSecret;
        }

        public string Sign(string stringToSign)
        {
            var crypto = new HMACSHA1(Encoding.UTF8.GetBytes(_awsSecret));
            string signature = Convert.ToBase64String(crypto.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
            return "AWS " + _awsKey + ":" + signature;
        }

        public string Sign(string verb, string url, NameValueCollection headers)
        {
            return Sign(verb, url, headers,
                        headers[KnownHttpHeaderNames.ContentMD5],
                        headers[KnownHttpHeaderNames.ContentType],
                        headers[KnownHttpHeaderNames.Date]);
        }

        public string Sign(string verb, string url, NameValueCollection headers,
                           string contentMD5, string contentType, string dateHeader)
        {
            string stringToSign = (BuildStandardHeaders(verb, contentMD5, contentType, dateHeader)
                                   + GetCanonicalizedAmzHeaders(headers)
                                   + GetCanonicalizedUrl(url));
            if (headers != null)
                headers.Add("x-debug-string-to-sign", HttpUtility.UrlEncode(stringToSign));
            return Sign(stringToSign);
        }

        public string GetCanonicalizedAmzHeaders(NameValueCollection headers)
        {
            if (headers == null)
                return string.Empty;

            var sortedNames = new List<string>(headers.AllKeys);
            sortedNames.Sort(LCased);
            string sts = string.Empty;
            foreach (string name in sortedNames)
                if (name.StartsWith(_signHeadersStartingWith, StringComparison.InvariantCultureIgnoreCase))
                    sts += name.ToLower(CultureInfo.InvariantCulture) + ":" + headers[name] + _newLine;
            return sts;
        }

        private static int LCased(string x, string y)
        {
            return x.ToLower(CultureInfo.InvariantCulture).CompareTo(y.ToLower(CultureInfo.InvariantCulture));
        }

        public string BuildStandardHeaders(string verb, string contentMD5, string contentType, string dateHeader)
        {
            return verb + _newLine + contentMD5 + _newLine + contentType + _newLine + dateHeader + _newLine;
        }

        public string GetCanonicalizedUrl(string url)
        {
            string resource = url.Replace("http://", string.Empty).Replace("https://", "");
            if (resource.StartsWith(_awsS3HostName))
                return resource.Replace(_awsS3HostName, string.Empty);

            resource = resource.Replace("." + _awsS3HostName, string.Empty);
            if (!resource.StartsWith("/"))
                resource = "/" + resource;

            if (resource.Contains("?"))
            {
                string query = resource.Substring(resource.IndexOf("?"));
                if (query != "?acl" && query != "?location")
                    resource = resource.Replace(query, string.Empty);
            }
            resource = new Regex(@"(^/[^/]+)(:\d+)(.*$)").Replace(resource, "$1$3");
            return resource;
        }
    }
}